#include "GridWorld.hxx"

#include <map>
#include <fstream>
#include <algorithm>

Grid_World::Grid_World()
	:  m_dimension(0), m_num_keys(0)
{
}

Grid_World::~Grid_World()
{
	for ( unsigned k = 0; k < m_actions.size(); k++ )
		delete m_actions[k];

}

std::vector<Action*> Grid_World::avaiableActions( State& state )
{
    std::vector<Action*> actions;
    for( unsigned i = 0; i < this->actions().size(); i++)
    {
        if( this->actions()[ i ]->applicable( &state ) )
        {
//                std::cout << "\t"<< this->actions()[ i ]->identifier() << " - " <<this->actions()[ i ]->to_string() << std::endl;
                actions.push_back(this->actions()[ i ]);
        }
    }
//    std::cout << "\n";
    return actions;
}

bool	Grid_World::load_from( std::string filename )
{
	std::ifstream input( filename.c_str() );

	if ( input.fail() )
	{
		std::cerr << "Could not open " << filename << "!!" << std::endl;
		std::exit(1);
	}

	char 				line_buffer[256000];
	std::string 			line;
	unsigned 			line_num = 1;

	std::map<unsigned, unsigned>	cell_costs;

	while ( !input.eof() )
	{
		input.getline( line_buffer, 255999, '\n' );
		line.assign( line_buffer );
		if (line.empty()) continue;
		switch( line_num )
		{
			case 1: // retrieve dimension
			{
				from_string( m_dimension, line, std::dec );

				// Create initial state cells
				for ( unsigned i = 0; i < m_dimension; i++ )
					for ( unsigned j = 0; j < m_dimension; j++ )
					{
						unsigned id = i * m_dimension + j;
						Cell* c = new Cell(id, i, j );
						m_initial_state.cells().push_back( c );
						Cell* c_final = new Cell(id, i, j );

						m_final_state.cells().push_back( c_final );
					}
				break;
			}
			case 2: // retrieve number of keys
			{
				from_string( m_num_keys, line, std::dec );
				break;
			}
			case 3: // retrieve coordinates of agent
			{
				unsigned start_i;
				unsigned start_j;
				// split on comma
				std::string::iterator it = std::find( line.begin(), line.end(), ',' );
				std::string i_str( line.begin(), it++ );
				std::string j_str( it, line.end() );
				from_string( start_i, i_str, std::dec );
				from_string( start_j, j_str, std::dec );
				unsigned id_start_cell = start_i * m_dimension + start_j; 
				
				//create Agent initial state
				m_initial_state.agent() = new Agent( m_initial_state.cells()[ id_start_cell ] );

				//create Agent final state same location
				m_final_state.agent() = new Agent( m_initial_state.cells()[ id_start_cell ] );

				break;
			}
			default: // process key info && doors locked
			{
				
				//Process keys information
				if(line_num <= m_num_keys + 3)
				{
				
					// split on whitespace
					std::string::iterator it = std::find( line.begin(), line.end(), ' ' );
					std::string key_id_str( line.begin(), it++ );
					
					std::string::iterator it_next = std::find( it, line.end(), ' ' );
					std::string key_shape_str( it, it_next++ );					
					it = it_next;
					it_next = std::find( it, line.end(), ' ' );
					std::string start_pos( it, it_next++ );
					//split position on comma
					std::string::iterator it_pos = std::find( start_pos.begin(), start_pos.end(), ',' );
					std::string start_i_str( start_pos.begin(), it_pos++ );
					std::string start_j_str( it_pos, start_pos.end() );
					it=it_next;
					std::string target_pos( it, line.end() );
					//split position on comma
					it_pos = std::find( target_pos.begin(), target_pos.end(), ',' );
					std::string target_i_str( target_pos.begin(), it_pos++ );
					std::string target_j_str( it_pos, target_pos.end() );
					
					unsigned key_id;
					unsigned start_i;
					unsigned start_j;
					unsigned target_i;
					unsigned target_j;
					
					//string to type
					from_string( key_id, key_id_str, std::dec );
					from_string( start_i, start_i_str, std::dec );
					from_string( start_j, start_j_str, std::dec );
					unsigned id_start_cell = start_i * m_dimension + start_j;
					from_string( target_i, target_i_str, std::dec );
					from_string( target_j, target_j_str, std::dec );
					unsigned id_target_cell = target_i * m_dimension + target_j;
					eShape   shape;
					switch ( * key_shape_str.c_str() )
					{
					case '*': 
						shape = star;
						break;
					case '+': 
						shape = plus;
						break;						
					case '-': 
						shape = minus;
						break;
					case '/': 
						shape = slash;
						break;	
					}
					
					//Create Key start location
					m_initial_state.keys().push_back( new Key( key_id, shape, m_initial_state.cells()[ id_start_cell ] ) );
					
					//Create Key target location
					m_final_state.keys().push_back( new Key( key_id, shape, m_final_state.cells()[ id_target_cell ] ) );
				}
				else //lock cells start state, unlock final state
				{
					// split on whitespace
					std::string::iterator it = std::find( line.begin(), line.end(), ' ' );
					std::string cell_pos_str( line.begin(), it++ );
					std::string::iterator it_pos = std::find( cell_pos_str.begin(), cell_pos_str.end(), ',' );
					std::string i_str( cell_pos_str.begin(), it_pos++ );
					std::string j_str( it_pos, cell_pos_str.end() );					
					std::string keyhole_shape_str( it, line.end());
					unsigned cell_i, cell_j;
					from_string( cell_i, i_str, std::dec );
					from_string( cell_j, j_str, std::dec );
					unsigned id_cell = cell_i * m_dimension + cell_j;
					eShape   shape;
					switch ( * keyhole_shape_str.c_str() )
					{
					case '*': 
						shape = star;
						break;
					case '+': 
						shape = plus;
						break;						
					case '-': 
						shape = minus;
						break;
					case '/': 
						shape = slash;
						break;	
					}
					
					//set keyhole and lock cells
					m_initial_state.cells()[ id_cell ]->keyhole() = shape;
					m_final_state.cells()[ id_cell ]->keyhole() = shape;
										
					m_initial_state.cells()[ id_cell ]->locked() = true;
					m_final_state.cells()[ id_cell ]->locked() = false;
					
				}
				
				
				
				break;
			}
		}
		line_num++;
	}

	input.close();
 
	// Make actions
	unsigned id_action = 0;

	// Moves for all cells
	for ( unsigned i = 0; i < m_dimension; i++ )
		for ( unsigned j = 0; j < m_dimension; j++ )
		{
			unsigned src_id = i * m_dimension + j;
			unsigned dst_id;
			

			if ( i > 0 ) // top
			{
				dst_id = (i-1) * m_dimension + j;
				std::stringstream ss;
				ss <<  "Move(" << i << "," << j <<" --> " << i-1 << "," << j <<")";
				m_actions.push_back( new Move( src_id, dst_id, id_action++, ss.str() ) );
			}
			if ( i < m_dimension-1 ) // bottom
			{
				dst_id = (i+1) * m_dimension + j;
				std::stringstream ss;
				ss<< "Move(" << i << "," << j <<" --> " << i+1 << "," << j <<")";
				m_actions.push_back( new Move( src_id, dst_id, id_action++, ss.str() ) );
			}
			if ( j > 0 ) // left
			{
				dst_id = i * m_dimension + (j-1);
				std::stringstream ss;
				ss<< "Move(" << i << "," << j <<" --> " << i << "," << j-1 <<")";	
				m_actions.push_back( new Move( src_id, dst_id, id_action++, ss.str() ) );
			}
			if ( j < m_dimension-1 ) // right
			{
				dst_id = i * m_dimension + (j+1);
				std::stringstream ss;
				ss<< "Move(" << i << "," << j <<" --> " << i << "," << j+1 <<")";
				m_actions.push_back( new Move( src_id, dst_id, id_action++, ss.str() ) );
			}
		}

	//PickUps and Putdowns in all cells and keys
	for ( unsigned i = 0; i < m_dimension; i++ )
		for ( unsigned j = 0; j < m_dimension; j++ )
		{
			unsigned cell_id = i * m_dimension + j;
			
			
			for( unsigned key_id = 0; key_id < m_num_keys; key_id++)
			{				
				std::stringstream ss1;
				ss1 <<  "PickUp(" << i << "," << j << ",k"<< key_id <<")";			
				std::stringstream ss2;
				ss2 <<  "Putdown(" << i << "," << j << ",k"<< key_id <<")";			
				m_actions.push_back( new PickUp( cell_id, key_id, id_action++, ss1.str() ) );
				m_actions.push_back( new PutDown( cell_id, key_id, id_action++, ss2.str() ) );
			}
		}

	//Opens for every locked cell and with every possible key that matches the keyhole
	for ( unsigned i = 0; i < m_dimension; i++ )
		for ( unsigned j = 0; j < m_dimension; j++ )
		{
			unsigned src_id = i * m_dimension + j;
			unsigned dst_id;

			if ( i > 0 ) // top
			{				
				dst_id = (i-1) * m_dimension + j;
				Cell* c = m_initial_state.cells()[ dst_id ];
				if( c->locked() )
					for( unsigned key_id = 0; key_id < m_num_keys; key_id++)
						if( c->keyhole() == m_initial_state.keys()[ key_id ]->shape())
						{
							std::stringstream ss;
							ss <<  "Open(" << i << "," << j << " --> " << i-1 << "," << j << ",k"<< key_id <<")";
							m_actions.push_back( new Open( src_id, dst_id, key_id, id_action++, ss.str() ) );		
						}
			}
			if ( i < m_dimension-1 ) // bottom
			{
				dst_id = (i+1) * m_dimension + j;
				Cell* c = m_initial_state.cells()[ dst_id ];
				if( c->locked() )
					for( unsigned key_id = 0; key_id < m_num_keys; key_id++)
						if( c->keyhole() == m_initial_state.keys()[ key_id ]->shape())
						{
							std::stringstream ss;
							ss <<  "Open(" << i << "," << j << " --> " << i+1 << "," << j << ",k"<< key_id <<")";
							m_actions.push_back( new Open( src_id, dst_id, key_id, id_action++, ss.str() ) );		
						}
			}
			if ( j > 0 ) // left
			{
				dst_id = i * m_dimension + (j-1);	
				Cell* c = m_initial_state.cells()[ dst_id ];
				if( c->locked() )
					for( unsigned key_id = 0; key_id < m_num_keys; key_id++)
						if( c->keyhole() == m_initial_state.keys()[ key_id ]->shape())
						{
							std::stringstream ss;
							ss <<  "Open(" << i << "," << j << " --> " << i << "," << j-1 << ",k"<< key_id <<")";
							m_actions.push_back( new Open( src_id, dst_id, key_id, id_action++, ss.str() ) );		
						}	
			}
			if ( j < m_dimension-1 ) // right
			{
				dst_id = i * m_dimension + (j+1);
				Cell* c = m_initial_state.cells()[ dst_id ];
				if( c->locked() )
					for( unsigned key_id = 0; key_id < m_num_keys; key_id++)
						if( c->keyhole() == m_initial_state.keys()[ key_id ]->shape())
						{
							std::stringstream ss;
							ss <<  "Open(" << i << "," << j << " --> " << i << "," << j+1 << ",k"<< key_id <<")";
							m_actions.push_back( new Open( src_id, dst_id, key_id, id_action++, ss.str() ) );		
						}
			}	
		}

	
	return true;
        
}
